# coding: utf8
"""
    2. pip install aliyun-python-sdk-core -- 阿里云核心库     依赖链接：https://pypi.org/project/aliyun-python-sdk-core
    3. pip install aliyun-python-sdk-alidns --DNS云解析      依赖链接：https://pypi.org/project/aliyun-python-sdk-alidns

"""
try:
    import sys, aliyunsdkcore, aliyunsdkalidns
except:
    print("依赖包未安装", __file__)



import json
import aliyunsdkcore
from aliyunsdkcore.client import AcsClient
from aliyunsdkcore.acs_exception.exceptions import ServerException
# from aliyunsdkcore.acs_exception.exceptions import ClientException

# 获取域名解析列表
from aliyunsdkalidns.request.v20150109.DescribeDomainRecordsRequest import DescribeDomainRecordsRequest
# RecordID获取解析记录
from aliyunsdkalidns.request.v20150109.DescribeDomainRecordInfoRequest import DescribeDomainRecordInfoRequest
# 获取子域名解析列表
from aliyunsdkalidns.request.v20150109.DescribeSubDomainRecordsRequest import DescribeSubDomainRecordsRequest
# 添加域名解析记录
from aliyunsdkalidns.request.v20150109.AddDomainRecordRequest import AddDomainRecordRequest
# 删除解析记录
from aliyunsdkalidns.request.v20150109.DeleteDomainRecordRequest import DeleteDomainRecordRequest
from aliyunsdkalidns.request.v20150109.DeleteSubDomainRecordsRequest import DeleteSubDomainRecordsRequest
# 修改（更新）解析记录 1. 值 2. 备注
from aliyunsdkalidns.request.v20150109.UpdateDomainRecordRequest import UpdateDomainRecordRequest
from aliyunsdkalidns.request.v20150109.UpdateDomainRecordRemarkRequest import UpdateDomainRecordRemarkRequest
# 设置解析状态
from aliyunsdkalidns.request.v20150109.SetDomainRecordStatusRequest import SetDomainRecordStatusRequest
# 获取操作日志
from aliyunsdkalidns.request.v20150109.DescribeRecordLogsRequest import DescribeRecordLogsRequest

# local
from cloud_server.ali_exception import *


class AliyunDNS:
    """
    阿里云的DNS的相关操作：
        1. 获取解析记录：
            1.1. ail_dns_get_log -- 未经处理的主域名解析记录，全部json
            1.2. ali_dns_get_list  -- 处理过的一个列表，列表的每一项是一个字典  --  ali_dns_get_list
                    常用的键：["RR", "Value", "Type", "DomainName"]
            1.3. ali_dns_get_one_record -- 通过 RecordID 获取一个解析信息。
            1.4. ali_dns_get_subDomain -- 未经处理的子域名解析记录，全部json
        2. 添加解析记录
            ali_dns_add_record  --  添加失败不会返回错误，注意错误收集
        3. 修改解析记录
            ali_dns_update_record
            ali_dns_update_remark
        4. 删除解析记录
            ali_dns_del_record
            ali_dns_del_rr
        5. 查询解析记录的操作记录
            5.1 ali_dns_operationLogs       -- 单页的记录，可以指定时间
            5.2 ali_dns_all_operationLogs   -- 全部的操作记录
        5. 修改域名的DNS服务器
            pass

    Exception: InvalidParameter; DomainRecordNotBelongToUser
    """
    _DNS_AccessKeyID = 'LTAI4FddEwU9wsyM7HAMbjFh'
    _DNS_AccessKeySecret = 'U8ozmLBXvikyAN2sSOwvvxpW4Oiwu7'

    def ali_set_access_info(self, id_, secret_):
        """重置 AccessKeyID 和 AccessKeySecret."""
        self._DNS_AccessKeyID = id_
        self._DNS_AccessKeySecret = secret_

    def ali_dns_get_records(self, _domain, _page_number=1, _page_size=100,
                            _rr_keyword=None, _type_keyword=None, _value_keyword=None):
        """
        获取一个域名的解析列表
        # 使用方法： https://help.aliyun.com/document_detail/29776.html
        :param _page_size:      本次查询多少条
        :param _page_number:    本次查询的位置
        :param _value_keyword:  按记录值检索
        :param _type_keyword:   按记录类型检索
        :param _rr_keyword:     按主机记录检索
        :param _domain: 符合Access的域名。
        :return: dict 从服务器返回的json数据 - 全部的未经处理的原文.
        """
        client = AcsClient(self._DNS_AccessKeyID, self._DNS_AccessKeySecret)  # 创建安全数据链接
        request = DescribeDomainRecordsRequest()  # 创建请求对象
        request.set_accept_format('json')  # 返回格式
        request.set_DomainName(_domain)  # 设置 拉取列表的域名；
        request.set_PageNumber(int(_page_number))
        request.set_PageSize(int(_page_size))
        # 可选参数
        if _rr_keyword is not None: request.set_RRKeyWord(_rr_keyword)
        if _type_keyword is not None: request.set_TypeKeyWord(_type_keyword)
        if _value_keyword is not None: request.set_ValueKeyWord(_value_keyword)

        response = client.do_action_with_exception(request)  # 发出安请求，并返回数据
        return json.loads(str(response, encoding='utf-8'))  # 格式转换，并返回

    def ali_dns_get_list(self, _domain):
        """
        返回解析列表 - 表格式如下：
        [{"RR": "blog",             主机记录
         "Status": "ENABLE",        解析状态
         "Value": "39.98160",       记录值
         "Weight": 1,               负载均衡权重。
         "RecordId": "18676",       解析记录ID # 不常用
         "Type": "A",               记录类型 - A, AAAA, CNAME, MX,
         "DomainName":"leecq.xyz",  域名名称;
         "Locked": false,           当前解析记录锁定状态
         "Line": "default",         解析线路
         "TTL": 600,                生存时间: 600
         }, ]
        :param _domain:
        :return: [{内容详见上面},]
        """
        def get_all(_func, _domain):
            _n, _return = 0, []
            while True:
                _n += 1
                res = _func(_domain, _page_number=_n)
                _return.extend(res["DomainRecords"]["Record"])
                if not int(res["PageNumber"]) * int(res["PageSize"]) <= int(res["TotalCount"]):
                    return _return
        try:
            return get_all(self.ali_dns_get_records, _domain)
        except aliyunsdkcore.acs_exception.exceptions.ServerException:
            return get_all(self.ali_dns_get_subDomain, _domain)

    def ali_dns_get_one_record(self, _record_id):
        """
        可以在没有Access的情况下，通过 RecordID 获取解析记录单条；
        # 使用方法： https://help.aliyun.com/document_detail/29777.html
        :param _record_id: 解析记录的ID 。 此参数在添加解析时会返回，在获取域名解析列表时会返回。
        :return: dict {
         "RR": "blog",             主机记录
         "Status": "ENABLE",        解析状态
         "Value": "39.98160",       记录值
         "Weight": 1,               负载均衡权重。
         "RecordId": "18676",       解析记录ID # 不常用
         "Type": "A",               记录类型 - A, AAAA, CNAME, MX,
         "DomainName":"leecq.xyz",  域名名称;
         "Locked": false,           当前解析记录锁定状态
         "Line": "default",         解析线路
         "TTL": 600,                生存时间: 600
         }
        """
        client = AcsClient(self._DNS_AccessKeyID, self._DNS_AccessKeySecret)  # 构建 客户端
        request = DescribeDomainRecordInfoRequest()  # 构建 请求对象
        request.set_accept_format('json')  # 设置返回 类型
        request.set_RecordId(_record_id)  # 传递 RecordID信息
        response = client.do_action_with_exception(request)  # 发送请求并返回结果
        return json.loads(str(response, encoding='utf-8'))

    def ali_dns_get_subDomain(self, _sub_domain):
        """
        获取子域名的解析记录；
        # 使用方法：https://help.aliyun.com/document_detail/29778.html
        :param _sub_domain: 子域名
        :return: 同 ali_dns_get_log
        """
        client = AcsClient(self._DNS_AccessKeyID, self._DNS_AccessKeySecret)  # 构建 客户端
        request = DescribeSubDomainRecordsRequest()
        request.set_accept_format('json')
        response = client.do_action_with_exception(request)
        return json.loads(str(response, encoding='utf-8'))

    def ali_dns_add_record(self, _domain, _rr, _type, _value,
                           _language='en', _ttl=600, _priority=1, _line='default', **kwargs):
        """
        给指定的域名中添加一条 解析记录. #_# 注意错误收集
        解析记录  使用方法：https://help.aliyun.com/document_detail/29772.html
        解析记录  类型约束：https://help.aliyun.com/document_detail/29814.html
        解析记录  格式类型：https://help.aliyun.com/document_detail/29805.html

        :param str _domain:         要添加的域名
        :param str _rr:             主机记录 -- [@(主域名)]
        :param str _type:           记录类型 -- [A, AAAA, MX, CNAME, TXT, REDIRECT_URL, FORWARD_URL]
                                    详细信息：https://help.aliyun.com/document_detail/29805.html
        :param str _value:          需要与 @_type: 相对应；
        :param str _language: ='en' 用户语言
        :param int _ttl: =600       LTL-生存时间
        :param int _priority: =1    MX记录优先级；权重
        :param _line: = 'default'   解析线路
        :return tuple:              成功返回：{"RecordId":"记录ID","RequestId":"请求ID"}
                                    失败返回:{"RequestId": "请求ID", "HostId": "请求的Host",
                                    "Code": "错误代码", "Message": "错误的消息（原因）" }
        """
        # 创建对象
        client = AcsClient(self._DNS_AccessKeyID, self._DNS_AccessKeySecret)
        request = AddDomainRecordRequest()
        # 设置 请求 值
        request.set_accept_format('json')
        request.set_DomainName(_domain)
        request.set_RR(_rr)
        request.set_Type(_type)
        request.set_Value(_value)

        request.set_Lang(_language)
        request.set_TTL(_ttl)
        request.set_Priority(_priority)
        request.set_Line(_line)
        if 'UserClientIp' in kwargs.keys(): request.set_UserClientIp(kwargs['UserClientIp'])

        response = client.do_action_with_exception(request)
        return json.loads(str(response, encoding='utf-8'))

    def ali_dns_del_record(self, _record_id, _language='en', **kwargs):
        """
        删除一个 解析记录，通过 Record_ID
        使用方法： https://help.aliyun.com/document_detail/29773.html
        :param _record_id: 解析记录ID  在dns_get_list中有返回值；
        :param _language: = 'en'
        :param kwargs:  **其他参数：'UserClientIp'有效
        :return: 不存在会报错: aliyunsdkcore.acs_exception.exceptions.ServerException
                {"RecordId": "12",   "RequestId": "2B4415FE-4DCE-4FCB-943D-F11263B1C53A"}
        """
        client = AcsClient(self._DNS_AccessKeyID, self._DNS_AccessKeySecret)

        request = DeleteDomainRecordRequest()
        request.set_accept_format('json')

        request.set_RecordId(_record_id)
        request.set_Lang(_language)
        if 'UserClientIp' in kwargs.keys(): request.set_UserClientIp(kwargs['UserClientIp'])

        response = client.do_action_with_exception(request)
        # python2:  print(response)
        return json.loads(str(response, encoding='utf-8'))

    def ali_dns_del_rr(self, _domain, _rr):
        """
        调用DeleteSubDomainRecords根据传入参数删除主机记录对应的解析记录。
        如果被删除的解析记录中存在锁定解析，则该锁定解析不会被删除。
        # 使用方法：https://help.aliyun.com/document_detail/29779.html
        :param _domain: 主域名
        :param _rr:     主机记录
        :return:
        """
        client = AcsClient(self._DNS_AccessKeyID, self._DNS_AccessKeySecret, 'cn-hangzhou')

        request = DeleteSubDomainRecordsRequest()
        request.set_accept_format('json')

        request.set_DomainName(_domain)
        request.set_RR(_rr)

        response = client.do_action_with_exception(request)
        return json.loads(str(response, encoding='utf-8'))

    def ali_dns_update_record(self, _record_id, _rr, _type, _value,
                              _ttl=600, _line='default', _priority=1):
        """
        修改（更新）DNS解析记录：
        # 使用方法： https://help.aliyun.com/document_detail/29774.html
        :param _record_id:      解析记录ID
        :param _rr:             主机记录
        :param _type:           记录类型：
        :param _value:          记录纸
        :param _ttl: = 600      生存时间
        :param _line:'default'  解析线路
        :param _priority:       MX记录优先级
        :return:                失败返回错误
                                成功返回{"RecordId":"", "RequestId":""}
        """
        client = AcsClient(self._DNS_AccessKeyID, self._DNS_AccessKeySecret, 'cn-hangzhou')

        request = UpdateDomainRecordRequest()
        request.set_accept_format('json')

        request.set_RecordId(_record_id)
        request.set_RR(_rr)
        request.set_Type(_type)
        request.set_Value(_value)
        request.set_TTL(_ttl)
        request.set_Priority(_priority)
        request.set_Line(_line)

        response = client.do_action_with_exception(request)
        return json.loads(str(response, encoding='utf-8'))

    def ali_dns_update_remark(self, _record_id, _remake):
        """
        调用UpdateDomainRecordRemark根据传入参数修改解析记录的备注。
        https://help.aliyun.com/document_detail/143569.html
        :param _record_id: 解析记录ID
        :param _remake: 备注
        :return:
        """

        client = AcsClient(self._DNS_AccessKeyID, self._DNS_AccessKeySecret, 'cn-hangzhou')

        request = UpdateDomainRecordRemarkRequest()
        request.set_accept_format('json')

        request.set_RecordId(_record_id)
        request.set_Remark(_remake)

        response = client.do_action_with_exception(request)
        return json.loads(str(response, encoding='utf-8'))

    def ali_dns_set_status_record(self, _record_id, _status):
        """
        https://help.aliyun.com/document_detail/29775.html
        :param _record_id:
        :param bool _status:
        :return:
        """
        if _status:
            _status = 'Enable'
        else:
            _status = 'Disable'
        client = AcsClient(self._DNS_AccessKeyID, self._DNS_AccessKeySecret, 'cn-hangzhou')

        request = SetDomainRecordStatusRequest()
        request.set_accept_format('json')

        request.set_RecordId(_record_id)
        request.set_Status(_status)

        response = client.do_action_with_exception(request)
        # python2:  print(response)
        return json.loads(str(response, encoding='utf-8'))

    def ali_dns_operateLogs(self, _domain,
                            _page_num=1, _page_size=20,
                            _start_time=None, _end_time=None, _keyword=None):
        """
        调用DescribeRecordLogs根据传入参数获取域名的解析操作日志。
        # 使用方法： https://help.aliyun.com/document_detail/29780.html
        :param _keyword:
        :param _start_time:
        :param _end_time:
        :param _domain: 查询的域名；
        :param int _page_num: 默认第一页；
        :param int _page_size: 默认每页20条；最大100条
        :return: dict {"PageNumber": 1, "TotalCount": 28, "PageSize": 10,"RecordLogs": { "RecordLog":[ 内容]}}
                  ** dict["RecordLogs"]["RecordLog"]
        """

        client = AcsClient(self._DNS_AccessKeyID, self._DNS_AccessKeySecret, 'cn-hangzhou')

        request = DescribeRecordLogsRequest()
        request.set_accept_format('json')

        request.set_DomainName("leecq.xyz")
        request.set_PageNumber(_page_num)
        request.set_PageSize(_page_size)
        if _keyword is not None: request.set_KeyWord(_keyword)
        if _start_time is not None: request.set_StartDate(_start_time)
        if _end_time is not None: request.set_endDate(_end_time)

        response = client.do_action_with_exception(request)
        return json.loads(str(response, encoding='utf-8'))

    def ali_dns_all_operateLogs(self, _domain):
        """
        获取全部的操作记录
        :param _domain: 域名
        :return list: 操作记录列表 - 列表中的每一项为一个元组 -- 格式如下：
            {"Action": "ADD",                       # 操作类型
             "Message": "Add resolution record. TXT record alidnscheck Default cda5137cd1a04fccb25af6c6ba707b13 ( TTL: 600)",  # 主要内容；
             "ActionTime": "2019-11-16T15:18Z",     # 时间
             "ClientIp": "58.47.50.58",             # 客户端IP
             "ActionTimestamp": 1573888733000       # 操作时间戳
             }
        """
        _n, _logs = 1, []

        while True:
            _info = self.ali_dns_operateLogs(_domain, _n, 10)
            _logs.extend(_info["RecordLogs"]["RecordLog"])
            if int(_info["PageNumber"]) * int(_info["PageSize"]) <= int(_info["TotalCount"]):
                _n += 1
                # print(_n, _info["PageNumber"], _info["PageSize"], _info["TotalCount"])
            else: break
        return _logs

    def ali_dns_lines(self):
        """
        获取解析线路列表。
        https://help.aliyun.com/document_detail/69017.html
        """
        pass

    def ali_set_dnsServer(self):
        """只能修改为aliyun.com 的默认DNS服务器；https://help.aliyun.com/document_detail/29754.html"""
        pass


class AliClientAPI:
    """

    """
    _i_RR = "请输入主机记录："
    _i_type = "请输入记录类型："
    _i_value = "请输入记录值："
    _i_domain = "请输入域名："
    _print_list = ("RecordId", "Status", "Locked", "DomainName", "Type", "RR", "Value")  # "TTL"

    def __init__(self):
        self._client = AliyunDNS()

    def format_output(self, obj, *args):
        """

        :param obj:     阿里云返回的 格式化列表
        :type obj list or dict
        :param args: 可以是        字典      {key: '', wight: int}
                    也可以是一个 元组或列表    [[keys,], wight]
        :type args dict or list or tuple or str or int
        :return: No Return>
        """
        def list_to_dict():
            _new_list = []
            args_ = list(args)
            try:
                if isinstance(args_[0], dict): _new_list = args_
                elif isinstance(args_[0], (list, tuple)):
                    for _a in args_:
                        if isinstance(_a[0], (str, list)) and isinstance(_a[1], int):
                            _new_list.append({'key': _a[0], 'wight': _a[1]})
                        elif isinstance(_a[0], str) and isinstance(_a[1], str):
                            _key, _w = [], int
                            for __a in _a:
                                if isinstance(__a, str): _key.append(__a)
                                if isinstance(__a, int):
                                    _w = __a
                                    break
                            _new_list.append({'key': _key, 'wight': _w})
                        elif isinstance(_a[0], str):
                            raise AliFormatOutputError("format_output() *args(不能以int为首)")
                elif isinstance(args_[0], str) and isinstance(args_[len(args_)-1], int):
                    tmp_list = []
                    for _a in args_:
                        if isinstance(_a, str): tmp_list.append(_a)
                    _new_list.append({'key': tmp_list, 'wight': args_[len(args_)-1]})
                return _new_list
            except IndexError: return []

        def _print_t(_i, _dic=None):
            if _dic is None:
                _dic = {_i: _i}
            args_ = list_to_dict()
            args_.append({'key': ['RR', 'Value'], 'wight': 14})
            args_.append({'key': 'RecordId', 'wight': 17})
            args_.append({'key': 'Type', 'wight': 6})

            for _args in args_:
                _args['key'] = _args['key'] if not isinstance(_args['key'], str) else [_args['key']]
                # print('****',_i, _args['key'], _i in _args['key'])
                if _i in _args['key']:
                    print(_dic[_i].ljust(int(_args['wight'])), end='\t')
                    return
            print(_dic[_i], end='\t')

        if not isinstance(obj, list): obj = [obj, ]
        for _i in self._print_list:
            _print_t(_i)
        print('\n' + '-'*100)
        for _d in obj:
            for _i in self._print_list:
                try:
                    _print_t(_i, _d)
                except:
                    print("No Keys", end='\t\t')
            print('')

    def _add_record(self):
        _rr = input(self._i_RR)
        _type = input(self._i_type)
        _value = input(self._i_value)
        _domain = input(self._i_domain)

        _result = self._client.ali_dns_add_record(_domain, _rr, _type, _value)

        try:
            return 'ok', _result["RecordId"]
        except:
            try:
                return _result["Code"], _result["Message"]
            except:
                return "Error: Unknown Error."

    def add_record(self):
        result = self._add_record()
        # result = ('ok', '18698029263515648')
        if result[0] == 'ok':
            print("解析记录填加成功。")
            try:
                _record_res = self._client.ali_dns_get_one_record(result[1])
                self.format_output(_record_res)
            except:
                pass

    def demand_all_record(self):
        _domain = input(self._i_domain)
        result = self._client.ali_dns_get_list(_domain=_domain)
        self.format_output(result)

    def del_record_use_RR(self):
        """"""
        while True:
            try:
                # _rr = input(self._i_RR[:-1] + '(输入"exit"退出):')
                _domain = input(self._i_domain[:-1]+'(此域名为包含主机记录的域名):')
                _do_list = _domain.split('.')
                _rr = _do_list[0]
                _domain = '.'.join(_do_list[1:])
                if _rr == 'exit':
                    print("用户退出...")
                    return
                res = self._client.ali_dns_del_rr(_domain, _rr)
                if res["TotalCount"] == '1':
                    print(f"成功删除域名：{_rr}.{_domain}")
                    return
                else:
                    print('*' * 50)
                    print('*' + f' 你遇到错误了：错误码为：{res["Code"]}'.center(48) + '*')
                    print('*' + res["Message"].center(48) + '*')
                    print('*' * 50)
            except: pass


if __name__ == '__main__':
    domain = 'leecq.club'
    a = AliClientAPI()
    # a.del_record_use_RR('test', 'leecq.club')

    # # a = AliyunDNS().ali_dns_add_record(domain, )
    # a = AliyunDNS().ali_dns_get_list(domain)
    # # a = AliyunDNS().ali_dns_del('123')
    # if isinstance(a, (list, tuple)):
    #     for _a in a:
    #         print(_a)
    # # a = type(a)
    # else:
    #     print(a)
    # print(len(a))
    # # print(AliyunDNS.ali_get_dns_list.__doc__)
